#include <ace/Log_Msg.h>

#include "Logger.h"
#include <iostream>
#include <fstream>
#include <sstream>

using namespace std;

namespace Tool
{
	const unsigned int Logger::MAX_LOG_DESCRIPTION = 4096; //1024;//256;

	string Logger::stripLeadingPath(const string& p_fileName)
	{		       
        // strip leading path

        string name = "";
#ifdef _WIN32
        const string delimiter("\\");
#endif
#ifdef _LINUX
		const string delimiter("/");
#endif

		string car;

		string::const_reverse_iterator rit;

        for(rit = p_fileName.rbegin(); rit != p_fileName.rend(); ++rit)
        {    

            car = *rit; 
             
            if (car == delimiter)
                break;
                
            name = car + name;
		}

		return name;
	}


	Logger::Logger()
	{
		string program = "Clever";

#ifndef _WIN32
		ACE_LOG_MSG->priority_mask(LM_TRACE | LM_DEBUG | LM_NOTICE | LM_INFO | 
			                       LM_WARNING | LM_ERROR | LM_EMERGENCY, 
			                       ACE_Log_Msg::PROCESS);
#endif

		// register to SysLog with exe name

        this->m_program = stripLeadingPath(program);

#ifndef _WIN32
		ACE_LOG_MSG->open(ACE_TEXT(this->m_program.c_str()), ACE_Log_Msg::SYSLOG, ACE_TEXT( this->m_program.c_str() ));

        ACE_LOG_MSG->set_flags(ACE_Log_Msg::STDERR);
#endif
	}


	Logger::~Logger()
	{
	}


	void Logger::log(LogLevel p_level, const char* p_file, const char* p_function, unsigned long p_line, ...)
	{
		assert((NULL != p_file) && (NULL != p_function));

		// fix TAO bug ?
        // ORB_init reset the name setting given to SYSLOG (localhost:20012 instead of program name !)
        // register to SysLog with exe name
#ifndef _WIN32
		ACE_LOG_MSG->open(ACE_TEXT(this->m_program.c_str()), ACE_Log_Msg::SYSLOG, ACE_TEXT(this->m_program.c_str()));
#endif

#ifdef _LINUX
        ACE_LOG_MSG->open(ACE_TEXT(this->m_program.c_str()), ACE_Log_Msg::STDERR, ACE_TEXT(this->m_program.c_str()));
#endif     

		va_list ap = NULL; 

		// va_start sets arg_ptr to the first optional argument in the list of arguments passed to the function. The argument arg_ptr must have
        // va_list type. The argument prev_param is the name of the required parameter immediately preceding the first optional argument in the argument list. 
		// The argument prev_param is the name of the required parameter immediately preceding the first optional argument in the argument list. If prev_param 
		// is declared with the register storage class, the macro's behavior is undefined. va_start must be used before va_arg is used for the first time.
		

        va_start(ap, p_line); 

		// va_arg retrieves a value of type from the location given by arg_ptr and increments arg_ptr to point to the next argument
		// in the list, using the size of type to determine where the next argument starts. va_arg can be used any number of times within
		// the function to retrieve arguments from the list.

		const char  *va_format = va_arg(ap, const char*);


        string fileName(p_file);
		fileName = this->stripLeadingPath(fileName);

		string functionName(p_function);

        long   fileLine(p_line);


        // ok fixed parameters fetched

        // now attack variant part - only C like functions can do that :-(
        // ap contains now the argument for the printf-like string contained in p_description

        char formattedDescr[MAX_LOG_DESCRIPTION + 1];

#ifdef _LINUX
        vsnprintf(formattedDescr, MAX_LOG_DESCRIPTION, va_format, ap);
#endif


#ifdef _WIN32
        vsprintf_s(formattedDescr, MAX_LOG_DESCRIPTION, va_format, ap);
#endif

		// After all arguments have been retrieved, va_end resets the pointer to NULL.
				
        va_end(ap);

        formattedDescr[MAX_LOG_DESCRIPTION-1] = 0;

		// build log stream
        ostringstream output;
#ifdef _WIN32
		ostringstream outputWin;
#endif

        // bin file + description
        output << "[" << this->m_program << "] " << string(formattedDescr) << "  [ ";

		//
        // src file
        output << fileName;		
        // line
        output << " - line " << fileLine;
		// function
		output << " (" << functionName << ")";

#ifdef _WIN32

        switch(p_level)
		{
		case LG_DEBUG:
			outputWin << "[DEBUG]   ";
			break;
		case LG_INFO:
			outputWin << "[INFO]    ";
			break;
		case LG_WARNING:
			outputWin << "[WARNING] ";
			break;
		case LG_ERROR:
			outputWin << "[ERROR]   ";
			break;
		case LG_FATAL:
			outputWin << "[FATAL]   ";
			break;
		}

		outputWin << output.str();
		outputWin << " - pid " << GetCurrentProcessId() << " - tid " << GetCurrentThreadId(); 

#if _WIN32_WINNT >=  0x0600 // Vista
#ifdef VISTA_EXT_ENABLE
		outputWin << " - cpu " << GetCurrentProcessorNumber();
#endif
#endif

        outputWin << " ]" << endl;
#endif

		// ACE tag for process ID, thread ID
		output << " - pid %P - tid %t ]"; 
        output << endl;

#ifndef _WIN32
		switch(p_level)
		{
		case LG_DEBUG:
		    ACE_DEBUG( (LM_DEBUG, ACE_TEXT(output.str().c_str()) ) );
			break;

		case LG_INFO:
			ACE_ERROR( (LM_INFO, ACE_TEXT(output.str().c_str()) ) );
			break;

		case LG_WARNING:
			ACE_ERROR( (LM_NOTICE, ACE_TEXT(output.str().c_str()) ) );
			break;

		case LG_ERROR:
			ACE_ERROR( (LM_ERROR, ACE_TEXT(output.str().c_str()) ) );
			break;

		case LG_FATAL:
			ACE_ERROR( (LM_EMERGENCY, ACE_TEXT(output.str().c_str()) ) );
			break;

		default:
			break;
		}
#endif

// #ifdef _DEBUG
#ifdef _WIN32
        // get system time
		SYSTEMTIME st;
        GetSystemTime(&st);
		char timeStr[256];
		_snprintf_s(timeStr, 255, "%04d/%02d/%02d-GMT %02d:%02d:%02d - ", 
			     st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond);
 
		string outString = string(timeStr) + string(ACE_TEXT(outputWin.str().c_str()));

		OutputDebugStringA( outString.c_str() );
#endif
//#endif

	}	  


	string Logger::getProgramName() const
	{
		return m_program;
	}

	void Logger::setProgramName(std::string p_name)
	{
		m_program = p_name;
	}


#ifdef _WIN32
#ifdef _DIRECT3D9
	string Logger::getD3DErrorString(HRESULT p_input) const
	{
		string errorMsg = "";
		
		switch(p_input)
		{
		case D3D_OK:
            errorMsg = "No error occurred.";
			break;

		case D3DOK_NOAUTOGEN:
            errorMsg = "This is a success code. However, the autogeneration of mipmaps is not supported for this format. This means that resource creation will succeed but the mipmap levels will not be automatically generated.";
			break;

		case D3DERR_CONFLICTINGRENDERSTATE:
            errorMsg = "The currently set render states cannot be used together.";
			break;

		case D3DERR_CONFLICTINGTEXTUREFILTER:
            errorMsg = "The current texture filters cannot be used together.";
			break;

		case D3DERR_CONFLICTINGTEXTUREPALETTE:
            errorMsg = "The current textures cannot be used simultaneously.";
			break;

		case D3DERR_DEVICELOST:
            errorMsg = "The device has been lost but cannot be reset at this time. Therefore, rendering is not possible.";
			break;
    
		case D3DERR_DEVICENOTRESET:
            errorMsg = "The device has been lost but can be reset at this time.";
			break;

		case D3DERR_DRIVERINTERNALERROR:
            errorMsg = "Internal driver error. Applications should destroy and recreate the device when receiving this error. For hints on debugging this error, see Driver Internal Errors.";
            break;

		case D3DERR_DRIVERINVALIDCALL:
            errorMsg = "Not used.";
			break;

		case D3DERR_INVALIDCALL:
            errorMsg = "The method call is invalid. For example, a method's parameter may not be a valid pointer.";
			break;

		case D3DERR_INVALIDDEVICE:
            errorMsg = "The requested device type is not valid.";
			break;

		case D3DERR_MOREDATA:
            errorMsg = "There is more data available than the specified buffer size can hold.";
			break;

		case D3DERR_NOTAVAILABLE:
            errorMsg = "This device does not support the queried technique.";
			break;

		case D3DERR_NOTFOUND:
            errorMsg = "The requested item was not found.";
			break;

		case D3DERR_OUTOFVIDEOMEMORY:
            errorMsg = "Direct3D does not have enough display memory to perform the operation.";
			break;

		case D3DERR_TOOMANYOPERATIONS:
            errorMsg = "The application is requesting more texture-filtering operations than the device supports.";
			break;

		case D3DERR_UNSUPPORTEDALPHAARG:
            errorMsg = "The device does not support a specified texture-blending argument for the alpha channel.";
			break;

		case D3DERR_UNSUPPORTEDALPHAOPERATION:
            errorMsg = "The device does not support a specified texture-blending operation for the alpha channel.";
			break;

		case D3DERR_UNSUPPORTEDCOLORARG:
            errorMsg = "The device does not support a specified texture-blending argument for color values.";
			break;

		case D3DERR_UNSUPPORTEDCOLOROPERATION:
            errorMsg = "The device does not support a specified texture-blending operation for color values.";
			break;

		case D3DERR_UNSUPPORTEDFACTORVALUE:
            errorMsg = "The device does not support the specified texture factor value. Not used; provided only to support older drivers.";
			break;

		case D3DERR_UNSUPPORTEDTEXTUREFILTER:
            errorMsg = "The device does not support the specified texture filter.";
			break;

		case D3DERR_WASSTILLDRAWING:
            errorMsg = "The previous blit operation that is transferring information to or from this surface is incomplete.";
			break;

		case D3DERR_WRONGTEXTUREFORMAT:
            errorMsg = "The pixel format of the texture surface is not valid.";
			break;

		case E_FAIL:
            errorMsg = "An undetermined error occurred inside the Direct3D subsystem.";
			break;

		case E_INVALIDARG:
            errorMsg = "An invalid parameter was passed to the returning function.";
			break;

		case E_NOINTERFACE:
            errorMsg = "No object interface is available.";
			break;

		case E_NOTIMPL:
			errorMsg = "Not implemented.";
			break;

		case E_OUTOFMEMORY:
            errorMsg = "Direct3D could not allocate sufficient memory to complete the call.";
			break;

		default:
			errorMsg = "Unknown D3D error";
			break;

		}

		return errorMsg;
	}
#endif
#endif








	Trace::Trace (const ACE_TCHAR *prefix, const ACE_TCHAR *name, const ACE_TCHAR *func, int line, const ACE_TCHAR *file)
	{
		this->prefix_ = prefix;
		this->name_   = name;
		this->line_   = line;
		this->file_   = file;
		this->func_   = func;

#ifndef _WIN32
		ACE_Log_Msg *lm = ACE_LOG_MSG;
		if (lm->tracing_enabled () && lm->trace_active () == 0)
		{
			lm->trace_active (1);

			// fix TAO bug ?
			// ORB_init reset the name setting given to SYSLOG (localhost:20012 instead of program name !)
			// register to SysLog with exe name
			// lm->open(ACE_TEXT(::Tool::Logger::instance().getProgramName().c_str()), 
			//      ACE_Log_Msg::SYSLOG, ACE_TEXT(::Tool::instance().getProgramName().c_str()));

			//ACE_DEBUG((LM_TRACE,
			//		   ACE_TEXT("%s%*s calling %s\n"),
			//		   this->prefix_,
			//		   Trace::nesting_indent_ * lm->inc(),
			//		   ACE_TEXT(""),
			//		   this->name_));

			ACE_DEBUG((LM_TRACE,
					   ACE_TEXT("%s%*s calling %s\n"),
					   this->prefix_,
					   Trace::nesting_indent_ * lm->inc(),
					   ACE_TEXT(""),
					   this->func_));

			lm->trace_active (0);
		} // end if
#endif

//#ifdef _DEBUG
#ifdef _WIN32
		// invoke Microsoft debug API
		//string output = "[DEBUG]   > calling " + string(this->prefix_) + " " + 
		//			    string(this->name_);

		string output = "[DEBUG]   > calling " + string(this->prefix_) + " " + 
					    string(this->func_);

		OutputDebugStringA( output.c_str() );
#endif
//#endif

	}

	void Trace::setLine (int line)
    {
        this->line_ = line;
    }


	Trace::~Trace (void)
    {

#ifndef _WIN32
        ACE_Log_Msg *lm = ACE_LOG_MSG;

        if (lm->tracing_enabled () && lm->trace_active () == 0)
        {
            lm->trace_active (1);

            // fix TAO bug ?
            // ORB_init reset the name setting given to SYSLOG (localhost:20012 instead of program name !)
            // register to SysLog with exe name
			//lm->open(::Tool::Logger::instance().getProgramName().c_str(), ACE_Log_Msg::SYSLOG, ACE_TEXT(::Tool::Logger::instance().getProgramName().c_str()));
                        
            //ACE_DEBUG((LM_TRACE,
            //           ACE_TEXT("%s%*s leaving %s\n"),
            //           this->prefix_,
            //           Trace::nesting_indent_ * lm->dec(),
            //           ACE_TEXT(""),
            //           this->name_));  

			ACE_DEBUG((LM_TRACE,
                       ACE_TEXT("%s%*s leaving %s\n"),
                       this->prefix_,
                       Trace::nesting_indent_ * lm->dec(),
                       ACE_TEXT(""),
                       this->func_));  
                                    
            lm->trace_active (0);
        } // end if
#endif

//#ifdef _DEBUG
#ifdef _WIN32
		// invoke Microsoft debug API
		//string output = "[DEBUG]   > leaving " + string(this->prefix_) + " " + 
		//			    string(this->name_);

		string output = "[DEBUG]   > leaving " + string(this->prefix_) + " " + 
					    string(this->func_);

		OutputDebugStringA( output.c_str() );
#endif
//#endif



    }



};
